package com.ijtunes;

import com.ijtunes.utils.Properties;
import com.ijtunes.utils.Property;
import com.ijtunes.filetypes.Track;
import com.ijtunes.filetypes.ITunesTrack;
import com.ijtunes.sources.FileCollectionBuilder;
import com.ijtunes.sources.ITunesSource;
import com.ijtunes.sources.TrackSource;
import com.ijtunes.utils.Action;
import com.ijtunes.utils.Actions;
import com.ijtunes.utils.Logger;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.logging.Level;
import org.apache.commons.lang.WordUtils;

/**
 * Main class for iJTunes.
 * @author Michael Sena
 * @version 1.1
 * @date 05/07/2010
 */
public final class Main {

    /** Track collection / input source. */
    private TrackSource source = null;
    /** Static instance of the tags to skip when finding genre. */
    private static final ArrayList<String> SKIPPED_TAGS =
        Properties.getArray(Property.SKIPPED_TAGS);
    /** Static instance of the tags to replace when finding genre. */
    private static final ReplacementTags REPLACEMENT_TAGS =
        new ReplacementTags(Properties.getArray(Property.REPLACEMENT_TAGS));

    /**
     * Main method.
     */
    private Main() {
        source = new ITunesSource();
        processSource();
    }

    /**
     * Starts processing each track.
     */
    private void processSource() {
        Track track = null;
        while (source.hasNext()) {
            track = source.getNext();
            processTrack(track);
            Actions.finishingActions();
        }

        // Scan for new tracks
        if (Properties.getBool(Property.ADD_NEW_FILES)) {
            addNewTracks();
        }

        // Display summary
        System.out.println("\n" + Actions.generateSummary());

        // Finish up
        Properties.save();
    }

    /**
     * Adds files to iTunes if they don't exist.
     */
    private void addNewTracks() {
        FileCollectionBuilder files =
            new FileCollectionBuilder(
            Properties.getArray(Property.SCAN_DIRECTORIES));
        LinkedList<String> filesToAdd = new LinkedList<String>();
        Iterator<java.io.File> it = files.getIterator();
        System.out.println("Checking for new files to add: ");
        while (it.hasNext()) {
            java.io.File newFile = it.next();
            if (!source.containsLocation(newFile.getAbsolutePath())) {
                filesToAdd.add(newFile.getAbsolutePath());
                logAction(Action.ADDED, null, newFile.getAbsolutePath());
                System.out.println();
            }
        }

        if (filesToAdd.size() > 0) {
            System.out.print("Adding "
                + filesToAdd.size()
                + " files.  Please wait, this may take some time...");
            iTunesCOM.getInstance().libraryPlaylist().addFiles(filesToAdd.toArray());
            System.out.println("done.");
        }
    }

    /**
     * Performs functions on a single track.
     * @param track Track to analyse
     */
    private void processTrack(final Track track) {
        // Perform various functions to tracks
        if (track.exists()) {
            // Functions that happen to all tracks
            if (track.isSupported()) {
                // Duplicate files
                if (Properties.getBool(Property.FIND_DUPLICATES)) {
                    checkDuplicates(track);
                }

                // Genre Finder
                if (Properties.getBool(Property.GENRE_SEARCH)) {
                    genreSearch(track);
                }

                // File organisation
                if (Properties.getBool(Property.ORGANISE_FILES)) {
                    organiseTrack(track);
                }
            }

            // !! iTunes tracks only !!
            if (track instanceof ITunesTrack) {
                // Play-count Sync
                if (Properties.getBool(Property.SYNC_PLAYCOUNT)) {
                    syncPlaycount((ITunesTrack) track);
                }
            }
        } else {
            doTrackMissing(track);
        }
    }

    /**
     * Synchronises an iTunes playcount with last.fm.
     * @param track Track to synchronise
     */
    private void syncPlaycount(final ITunesTrack track) {
        if (track.getPlayedCount() < track.getOnlinePlayedCount()) {
            track.setPlayedCount(track.getOnlinePlayedCount());
            logAction(Action.PLAYCOUNT_SYNCED,
                track,
                Integer.toString(track.getOnlinePlayedCount()));
        }
    }

    /**
     * FUNCTION: Checks whether a file already exists in the collection.
     * @param track Track to scan for
     */
    private void checkDuplicates(final Track track) {
        // TODO: Incomplete function find duplicates
        // Add it to the hash map and check for duplicate files
        if (source.hasTrack(track)) {
            double sizeOne = source.getTrack(track.getUniqueName()).getSize();
            double sizeTwo = track.getSize();

            if (sizeOne == sizeTwo) {
                System.out.println("Files are the same size.");
            } else if (sizeOne < sizeTwo) {
                System.out.println("File one is smaller: "
                    + sizeOne + " vs. " + sizeTwo);
            } else {
                if (!track.getUniqueName().contains(Track.DEFAULT_BLANK)) {
                    System.out.println(
                        "File two [" + track.getLocation()
                        + "] is smaller: "
                        + sizeTwo + " vs. " + sizeOne);
                } else {
                    Logger.log(this,
                        "Potential duplicate: "
                        + track.getLocation()
                        + " vs. "
                        + source.getTrack(track.getUniqueName()).getLocation(),
                        Level.INFO);
                }
            }
        }
        source.addTrack(track);
    }

    /**
     * Moves a track, based on a naming scheme and its meta-data.
     * @param track Track to organise
     */
    private void organiseTrack(final Track track) {
        // TODO: Re-organise organising files function
        String expressionLocation = null;
        expressionLocation = OrganisingExpression.form(track);

        if (expressionLocation != null) {
            if (!expressionLocation.equals(track.getLocation())) {
                File oldFile = new File(track.getLocation());
                File newFile = new File(expressionLocation);
                File parentDir = new File(newFile.getParent());
                if (!newFile.exists()) {
                    boolean paused = false;
                    // TODO: Fix errors when updating iTunes track
                    if ((track instanceof ITunesTrack) && ((ITunesTrack) track).isPlaying()) {
                        iTunesCOM.getInstance().pause();
                        paused = true;
                    }
                    if (!parentDir.exists()) {
                        if (!parentDir.mkdirs()) {
                            Logger.log(Main.class.getName(),
                                "Unable to create parent directories: "
                                + parentDir.getAbsolutePath(),
                                Level.WARNING);
                        }

                    }
                    if (parentDir.exists()) {
                        if (!oldFile.renameTo(newFile)) {
                            logAction(Action.ORGANISED,
                                track,
                                track.getLocation() + "->"
                                + expressionLocation);
                            if (Properties.getBool(Property.UPDATE_ITUNES)) {
                                ((ITunesTrack) track).setLocation(
                                    expressionLocation);
                            }
                        } else {
                            Logger.log(Main.class.getName(),
                                "Unable to organise file: "
                                + expressionLocation,
                                Level.SEVERE);
                        }
                    }
                    if (paused) {
                        iTunesCOM.getInstance().resume();
                    }
                } else {
                    Logger.log(Main.class.getName(),
                        "Unable to organise file, as destination "
                        + "already exists: " + expressionLocation,
                        Level.WARNING);
                }

            }
        }
    }

    /**
     * Performed when a track is missing from a collection.
     * @param track Missing track
     */
    private void doTrackMissing(final Track track) {
        if (track instanceof ITunesTrack
            && Properties.getBool(Property.DELETE_MISSING)) {
            logAction(Action.DELETED, track, null);
        } else {
            logAction(Action.MISSING, track, track.getLocation());
        }
    }

    /**
     * Uses Last.fm to work out an artist's genre.
     * @param track Track to use in the search
     */
    private void genreSearch(final Track track) {
        if (track.getGenre() == null
            || Properties.getBool(
            Property.REPLACE_EXISTING_GENRE)) {
            int iTagToUse = 0;

            // Initial replacements
            String tagToUse = "";


            while (iTagToUse < track.getTopTags().size()
                && (tagToUse.isEmpty()
                || SKIPPED_TAGS.contains(tagToUse)
                || tagToUse.equals(track.getArtist().toLowerCase()))) {
                if (++iTagToUse != track.getTopTags().size()) {
                    tagToUse = ((String) track.getTopTags().toArray()[iTagToUse]).toLowerCase();


                }
            }

            tagToUse = REPLACEMENT_TAGS.checkReplaceTag(track, tagToUse);
            tagToUse = WordUtils.capitalize(tagToUse);



            if (track.getGenre() == null || !track.getGenre().equals(tagToUse)) {
                track.setGenre(tagToUse);
                logAction(Action.GENRE_TAGGED, track, tagToUse);


            }
        }
    }

    /**
     * Main function.
     * @param args Arguments parsed into the JVM
     */
    public static void main(final String[] args) {
        new Main();
    }

    /**
     * Standard method of logging actions.
     * @param action Type of action that was performed
     * @param track Track the action was performed on
     * @param extra Additional information to display regarding the track
     */
    private void logAction(final Action action, final Track track,
        final String extra) {
        Actions.logAction(action, source, track, extra);
    }
}
